#include <QThread>
#include "Game.h"

/**
 * 构造方法。
 * @param gameBoard 游戏区域
 * @param bitmap    后台缓冲位图
 */
Game::Game(QPixmap* bitmap, QWidget* gameBoard) :
    mBitmap(bitmap),
    mBlackBrush(QColor(0, 0, 0)),
    mGameBoard(gameBoard),
    mIsBlack(true),
    mStatus(END),
    mWhiteBrush(QColor(255, 255, 255)) {
    // 重绘游戏区域
    this->drawGameBoard();

    // 连接信号和槽
    QTcpSocket::connect(this, SIGNAL(readyRead()), SLOT(onReceive()));

    // 连接服务器
    QTcpSocket::connectToHost(GomokuProtocol::SERVER_IP, GomokuProtocol::SERVER_PORT);
    bool result = QTcpSocket::waitForConnected(Const::SERVER_CONNECT_TIME_OUT);
    if (!result) {
        throw static_cast<int>(QTcpSocket::error());
    }
}

/**
 * 析构方法。
 */
Game::~Game() {
    // 发送退出游戏请求
    QuitGame quitGame(mUsername.toStdString().c_str());
    this->sendData(&quitGame, sizeof(quitGame));

    // 延迟一下，防止数据还没发送完成就关闭网络
    QThread::sleep(100);

    // 关闭网络
    QTcpSocket::close();
}

/**
 * 接受或拒绝挑战。
 * @param rivalName 对手的名字
 * @param accept    接受挑战则为 true，否则为 false
 */
void Game::acceptInvite(const char* rivalName, bool accept) {
    InviteResult result(accept, mUsername.toStdString().c_str(), rivalName);

    // 如果接受则游戏开始
    if (accept) {
        mRivalName = rivalName;
        this->start(false);
    }

    // 发送结果
    this->sendData(&result, sizeof(InviteResult));
}

/**
 * 检查横向。
 * @return 胜利则返回 true，否则返回 false
 */
bool Game::checkHorizontal() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量

    // 从左到右检查一行
    for (int i = 0; i < Const::GAME_BOARD_POINT_COUNT_H; i++) {
        // 查找棋子
        if (mPieces[mLastPos.y()][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return true;
        }
    }
    return false;
}

/**
 * 检查斜向（从左上到右下）。
 * @return 胜利则返回 true，否则返回 false
 */
bool Game::checkOblique1() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;                          // 要检查的棋子
    int count = 0;                                                              // 连续的棋子的数量
    const int min = mLastPos.x() < mLastPos.y() ? mLastPos.x(): mLastPos.y();  // 范围坐标，用以确定左边界和上边界
    const int left = mLastPos.x() - min;                                        // 左边界
    const int top = mLastPos.y() - min;                                         // 上边界

    // 从左上到右下检查一斜行
    for (int i = left, j = top; i < Const::GAME_BOARD_POINT_COUNT_H && j < Const::GAME_BOARD_POINT_COUNT_V; i++, j++) {
        // 查找棋子
        if (mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return true;
        }
    }
    return false;
}

/**
 * 检查斜向（从左下到右上）。
 * @return 胜利则返回 true，否则返回 false
 */
bool Game::checkOblique2() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量
    int left = 0;                                       // 左边界
    int bottom = 0;                                     // 下边界

    // 计算左边界和下边界
    if (mLastPos.x() + mLastPos.y() < Const::GAME_BOARD_POINT_COUNT_H) {
        bottom = mLastPos.x() + mLastPos.y();
    } else {
        left = mLastPos.x() + mLastPos.y() - Const::GAME_BOARD_POINT_COUNT_H + 1;
        bottom = Const::GAME_BOARD_POINT_COUNT_V - 1;
    }

    // 从左上到右上检查一斜行
    for (int i = left, j = bottom; i < Const::GAME_BOARD_POINT_COUNT_H && j >= 0; i++, j--) {
        // 查找棋子
        if (mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return true;
        }
    }
    return false;
}

/**
 * 检查纵向。
 * @return 胜利则返回 true，否则返回 false
 */
bool Game::checkVertical() {
    const Piece checkPiece = mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量

    // 从上到下检查一列
    for (int j = 0; j < Const::GAME_BOARD_POINT_COUNT_V; j++) {
        // 查找棋子
        if (mPieces[j][mLastPos.x()] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == Const::WIN_NUMBER) {
            return true;
        }
    }
    return false;
}

/**
 * 认输。
 */
void Game::defeat() {
    // 发送游戏结束数据
    class GameOver gameOver(SELF_ADMIT, mRivalName.toStdString().c_str());
    this->sendData(&gameOver, sizeof(class GameOver));

    // 游戏结束
    this->gameOver(SELF_ADMIT);
}

/**
 * 画游戏区域。
 */
void Game::drawGameBoard() {
    // 开始画图
    mDrawer.begin(mBitmap);

    // 画背景色
    mDrawer.fillRect(0, 0, Const::GAME_BOARD_WIDTH, Const::GAME_BOARD_HEIGHT, Const::GAME_BOARD_COLOR);

    // 画线
    for (int i = 1; i <= Const::GAME_BOARD_POINT_COUNT_H; i++) {
        // 画横线
        mDrawer.drawLine(Const::GRID_SIZE, i * Const::GRID_SIZE,
            Const::GAME_BOARD_WIDTH - Const::GRID_SIZE, i * Const::GRID_SIZE);

        // 画竖线
        mDrawer.drawLine(i * Const::GRID_SIZE, Const::GRID_SIZE,
            i * Const::GRID_SIZE, Const::GAME_BOARD_HEIGHT - Const::GRID_SIZE);
    }

    // 结束画图
    mDrawer.end();

    // 通知游戏区域重绘
    mGameBoard->update();
}

/**
 * 寻找其它用户。
 */
void Game::findUsers() {
    if (mUsername.isEmpty()) {
        return;
    }

    FindUsersRequest request(mUsername.toStdString().c_str());
    this->sendData(&request, sizeof(request));
}

/**
 * 游戏结束。
 * @param reason    结束原因
 */
void Game::gameOver(GameOverReason reason) {
    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    emit UM_GAME_OVER(reason);
}

/**
 * 处理寻找其它用户结果。
 * @param result 结果
 */
void Game::handleFindUsers(FindUsersResult* result) {
    // 发送通知
    emit UM_GET_FIND_USERS_RESULT(result);
}

/**
 * 处理游戏结束。
 * @param gameOver  游戏结束信息
 */
void Game::handleGameOver(GameOver* gameOver) {
    // 如果没在下棋中，则直接返回
    if (mStatus == END) {
        return;
    }

    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    GameOverReason reason = gameOver->mReason;
    if (gameOver->mReason == WON) {
        reason = LOST;
    } else if (gameOver->mReason == SELF_ADMIT) {
        reason = RIVAL_ADMIT;
    }
    emit UM_GAME_OVER(reason);
}

/**
 * 处理挑战请求。
 * @param request 请求
 */
void Game::handleInviteRequest(InviteRequest* request) {
    // 发送通知
    emit UM_GET_INVITE_REQUEST(request->mRivalName);
}

/**
 * 处理挑战结果。
 * @param result  结果
 */
void Game::handleInviteResult(InviteResult* result) {
    // 如果接受则游戏开始
    if (result->mAccept) {
        mRivalName = result->mRivalName;
        this->start(true);
    }

    // 发送通知
    emit UM_GET_INVITE_RESULT(result->mRivalName, result->mAccept);
}

/**
 * 处理登录结果。
 * @param result    结果
 */
void Game::handleLogon(LogonResult* result) {
    bool succeeded = result->mUsername[0] != '\0';
    if (succeeded) {
        // 保存用户名
        mUsername = result->mUsername;
    }

    // 发送通知
    emit UM_GET_LOGON_RESULT(succeeded);
}

/**
 * 处理放置棋子。
 * @param putPiece  棋子信息
 */
void Game::handlePutPiece(PutPiece* putPiece) {
    // 放一个与自己颜色相反的棋子
    this->putPiece(!mIsBlack, putPiece->mX, putPiece->mY);

    // 将状态设置为游戏中
    mStatus = PLAYING;
}

/**
 * 处理对手退出游戏事件。
 * @param quitGame  退出游戏信息
 */
void Game::handleQuitGame(QuitGame* quitGame) {
    // 如果不是当前对手，则直接返回
    if (mRivalName != quitGame->mUsername) {
        return;
    }

    // 发送通知
    bool isPlaying = mStatus != END;
    emit UM_RIVAL_QUIT_GAME(isPlaying, quitGame->mUsername);

    // 对手名置空
    mRivalName.clear();

    // 将状态设置为已结束
    mStatus = END;
}

/**
 * 发起挑战。
 * @param rivalName 要挑战的用户的名字
 */
void Game::invite(const char* rivalName) {
    if (rivalName == nullptr) {
        return;
    }

    // 发送挑战请求
    InviteRequest request(mUsername.toStdString().c_str(), rivalName);
    this->sendData(&request, sizeof(request));
}

/**
 * 判断是否胜利。
 * @return 胜利则返回 true，否则返回 false
 */
bool Game::isWon() {
    // 检查横向
    bool result = this->checkHorizontal();
    if (result) {
        return result;
    }

    // 检查纵向
    result = this->checkVertical();
    if (result) {
        return result;
    }

    // 检查斜向（从左上到右下）
    result = this->checkOblique1();
    if (result) {
        return result;
    }

    // 检查斜向（从左下到右上）
    result = this->checkOblique2();

    return result;
}

/**
 * 登录。
 * @param username  用户名
 */
void Game::logon(const char* username) {
    // 发送登录请求
    LogonRequest request(username);
    this->sendData(&request, sizeof(request));
}

/**
 * 游戏区域的单击响应方法。
 * @param x, y  用户单击的位置
 */
void Game::onGameBoardClicked(int x, int y) {
    const int bound = Const::GRID_SIZE / 2;   // 格的尺寸的一半

    // 如果不是己方下棋中（对手下棋中或游戏结束）或者 单击的范围超出棋盘，则直接返回
    if (mStatus != PLAYING ||
        x < bound || x > Const::GAME_BOARD_WIDTH - bound ||
        y < bound || y > Const::GAME_BOARD_HEIGHT - bound) {
        return;
    }

    // 将用户单击的点转换为离得最近的棋盘点
    int tempX = x;
    int tempY = y;
    int temp = x % Const::GRID_SIZE;
    if (temp >= bound) {
        tempX += Const::GRID_SIZE - temp;
    } else {
        tempX -= temp;
    }

    temp = y % Const::GRID_SIZE;
    if (temp >= bound) {
        tempY += Const::GRID_SIZE - temp;
    } else {
        tempY -= temp;
    }

    // 将窗口坐标转换成棋盘坐标
    tempX = tempX / Const::GRID_SIZE - 1;
    tempY = tempY / Const::GRID_SIZE - 1;

    // 放置棋子
    bool put = this->putPiece(mIsBlack, tempX, tempY);
    if (!put) {
        return;
    }

    // 判断是否胜利
    bool won = this->isWon();

    // 将已放置的棋子发给对手
    PutPiece putPiece = PutPiece(mRivalName.toStdString().c_str(), tempX, tempY);
    this->sendData(&putPiece, sizeof(putPiece));

    // 如果胜利则通知对手，否则等待对手下棋
    if (won) {
        // 发送游戏结束数据
        class GameOver gameOver(WON, mRivalName.toStdString().c_str());
        this->sendData(&gameOver, sizeof(GameOver));

        // 游戏结束
        this->gameOver(WON);
    } else {
        mStatus = RIVAL_PLAYING;
    }
}

/**
 * 处理服务器发来的数据。
 * @param pro   协议
 */
void Game::onGetMessageFromServer(GomokuProtocol* pro) {
    switch (pro->mType) {
        case GomokuProtocol::FIND_USERS:
            this->handleFindUsers(reinterpret_cast<FindUsersResult*>(pro));
            break;
        case GomokuProtocol::GAME_OVER:
            this->handleGameOver(reinterpret_cast<GameOver*>(pro));
            break;
        case GomokuProtocol::INVITE_REQUEST:
            this->handleInviteRequest(reinterpret_cast<InviteRequest*>(pro));
            break;
        case GomokuProtocol::INVITE_RESULT:
            this->handleInviteResult(reinterpret_cast<InviteResult*>(pro));
            break;
        case GomokuProtocol::LOGON:
            this->handleLogon(reinterpret_cast<LogonResult*>(pro));
            break;
        case GomokuProtocol::PUT_PIECE:
            this->handlePutPiece(reinterpret_cast<PutPiece*>(pro));
            break;
        case GomokuProtocol::QUIT_GAME:
            this->handleQuitGame(reinterpret_cast<QuitGame*>(pro));
            break;
    }
}

/**
 * 接收到数据的响应方法。
 */
void Game::onReceive() {
    // 接收数据长度
    qint64 dataSize = 0;
    qint64 read = QTcpSocket::read(reinterpret_cast<char*>(&dataSize), sizeof(qint64));
    if (read <= 0) {
        return;
    }

    // 接收数据
    char* buffer = new char[static_cast<size_t>(dataSize)];
    read = QTcpSocket::read(buffer, dataSize);
    if (read <= 0) {
        delete[] buffer;
        return;
    }

    // 将数据转换成协议
    GomokuProtocol* pro = reinterpret_cast<GomokuProtocol*>(buffer);

    // 处理协议
    this->onGetMessageFromServer(pro);

    // 释放缓存
    delete[] buffer;
}

/**
 * 放置棋子。
 * @param isBlack 黑棋则为 true，白棋则为 false
 * @param x, y    棋盘坐标
 * @return 成功则返回 true，否则返回 false
 */
bool Game::putPiece(bool isBlack, int x, int y) {
    // 如果超出棋盘区域，则返回 false
    if (x >= Const::GAME_BOARD_POINT_COUNT_H || y >= Const::GAME_BOARD_POINT_COUNT_V) {
        return false;
    }

    // 如果当前点已经有棋子了，则返回 false
    if (mPieces[y][x] != NOTHING) {
        return false;
    }

    // 画实心圆
    mDrawer.begin(mBitmap);
    if (isBlack) {
        mDrawer.setBrush(mBlackBrush);
    } else {
        mDrawer.setBrush(mWhiteBrush);
    }
    mDrawer.drawEllipse((x + 1) * Const::GRID_SIZE - Const::PIECE_RADIUS,
        (y + 1) * Const::GRID_SIZE - Const::PIECE_RADIUS, Const::PIECE_RADIUS * 2, Const::PIECE_RADIUS * 2);
    mDrawer.end();

    // 通知游戏区域重绘
    mGameBoard->update();

    // 保存最后放置棋子的位置
    mLastPos.setX(x);
    mLastPos.setY(y);

    // 保存到所有棋子中
    if (isBlack) {
        mPieces[y][x] = BLACK;
    } else {
        mPieces[y][x] = WHITE;
    }

    return true;
}

/**
 * 游戏重新开始。
 */
void Game::restart() {
    // 发送挑战请求
    InviteRequest request(mUsername.toStdString().c_str(), mRivalName.toStdString().c_str());
    this->sendData(&request, sizeof(request));
}

/**
 * 发送数据。
 * @param pro       协议
 * @param dataSize  数据长度
 */
void Game::sendData(GomokuProtocol* pro, qint64 dataSize) {
    // 发送数据长度
    QTcpSocket::write(reinterpret_cast<const char*>(&dataSize), sizeof(dataSize));

    // 发送数据
    QTcpSocket::write(reinterpret_cast<const char*>(pro), dataSize);
    QTcpSocket::flush();

    // 防止发送过快导致服务器无法响应
    QThread::msleep(100);
}

/**
 * 游戏开始。
 * @param isBlack 黑棋则为 true，白棋则为 false
 */
void Game::start(bool isBlack) {
    // 重绘游戏区域
    this->drawGameBoard();

    // 清空所有棋子
    memset(mPieces, 0, sizeof(mPieces));

    // 设置成员变量
    mStatus = isBlack ? PLAYING: RIVAL_PLAYING;
    mIsBlack = isBlack;

    // 发送通知
    emit UM_GAME_START(mRivalName.toStdString().c_str());
}
